import math;
from KnowledgeSource import KnowledgeSource;

class HmmSubject(object):
    def __init__(self, sArgSubject):
        self.aProbSt = [];
        self.aStates = [];
        self.aMaxSts = [];
        self.aTimes = [];
        self.aBestPath = [];
        self.iMultArgs = -1; #Temporary Patch (for consitency issue)
        self.mMultArgs = dict(); #Temporary Patch (for consitency issue)
        self.mBestPath = dict(); #Temporary Patch (for consitency issue)
        self.knowledgeSrc = KnowledgeSource();
        self.sArgSubject = sArgSubject;
    
    def getStates(self, sTime, aActs_Time):
        aSt = [];
        #Get All States that belong to this Subject (in this time step "t")
#         print "-", aActs_Time[sTime], 
        for actM in aActs_Time[(self.sArgSubject,sTime)]:
#             print "--", self.sArgSubject, actM.rootAct.aSubjects, sTime;
            for sArg in actM.rootAct.aSubjects:
                if sArg==self.sArgSubject:
                    aSt.append(actM);
#                     nNumArgs = self.getNumArgs(actM);
#                     if nNumArgs>1 and self.knowledgeSrc.getGroupValidation(actM.rootAct, "HmmSubject.getStates"):
#                         aSt.append(actM);
#                     elif nNumArgs==1:
#                         aSt.append(actM);
#         print "aSt:", aSt;
        self.aStates.append(aSt);        
    
    def getProbSt(self, iT, graph):
        print "\nTime:", self.aTimes[iT], "- HMM:", self.sArgSubject;
        aProbSt_T = [];
        aProbSt_T.append(self.getNullSt(iT, graph));
        for actM in self.aStates[iT]: #Update Prob on each Value-Act (of this Time Step)
            node = graph.idndict[actM.sId];
            print "State:", actM.rootAct.name;
            nObsProb = 1;
            for i in range(len(self.aStates[iT])): #Likelihood of each Obs given this Value-Act
                if self.aStates[iT][i]==actM:
#                    print "  -Likelihood:", actM.nLikelihood;
                    nObsProb *= actM.nLikelihood;
                else:
#                    print "  -Null Likelihood:", 0.1;
                    nObsProb *= 0.01; #Change 0.1 to Null Prob Model: P(Obs|~M)
            nSumProb = 0;
            nLinks = len(graph.ndict[node][0]);
            if nLinks==0:
                print "  -Prior Prob:", graph.ndict[node][1][0];
                nSumProb += graph.ndict[node][1][0];
            elif nLinks>0:
                print "  -Transition Probs:", graph.ndict[node][1];
                for pastSt in graph.ndict[node][0]:
                    print "  -PastSt:", pastSt.name;
                nPastActs = len(graph.ndict[node][0]);
                for i in range(2**nPastActs):
                    sBin = bin(i)[2:].zfill(nPastActs);
                    print "  -Trans_Prob:", graph.ndict[node][1][i], "PastForward:", self.getPastForward(sBin, graph.ndict[node][0], node.aSubjects);
#                    nSumProb += graph.ndict[node][1][2**i] * self.aProbSt[iT-1][i];
#                    nSumProb += graph.ndict[node][1][2**i] * graph.ndict[node][0][i].nHmmProb;
                    nSumProb += graph.ndict[node][1][i] * self.getPastForward(sBin, graph.ndict[node][0], node.aSubjects);
            print "aProbSt_T: nObsProb * nSumProb = ", nObsProb, "*", nSumProb, "=", nObsProb * nSumProb;
            aProbSt_T.append(nObsProb * nSumProb);
        nZ = 1.0/sum(aProbSt_T); #Normalization Constant
        for i in range(len(aProbSt_T)): #Normalize Probs
            aProbSt_T[i] *= nZ;
        self.aProbSt.append(aProbSt_T);
        for i, actM in enumerate(self.aStates[iT],1): #Store these Probs into their Nodes
            actM.rootAct.mHmmProb[self.sArgSubject] = aProbSt_T[i];
            actM.rootAct.mHmmNullProb[self.sArgSubject] = aProbSt_T[0]; #Store Null Prob
        print "ProbSt[iT]:", self.aProbSt[iT];        
        
    def getViterbi_ByLog(self, iT, graph):
#         print "\nTime:", self.aTimes[iT], "- HMM:", self.sArgSubject;
        aMax_Forward, aMaxPastAct = [], [];
        for actM in self.aStates[iT]: #For each State: Get Prob (MaxForward) of each PastSt in PastSt->This_State Activity (of this Time Step)
            nObsProb = actM.rootAct.aScores[0]; #Likelihood
            nodeInfoCPT = graph.ndict[actM.rootAct]; #CPT
#             print "State:", actM.rootAct.name;
            nLinks = len(nodeInfoCPT[0]);
            if nLinks==0:
                nPrior = nodeInfoCPT[1][0];
#                 nMax_TransFwd = math.log(nPrior);
                nMax_TransFwd = nPrior;
                maxPastAct = None;
            elif nLinks>0:
                nMax_TransFwd, maxPastAct = float("-inf"), None;
                for i,nTransP in enumerate(nodeInfoCPT[1]): #For each CPT entry value
                    if nTransP==0: continue;
                    nTransFwd, pastAct = 1, None;
                    for j,nB in enumerate(bin(i)[2:].zfill(nLinks)): #For each PastAct
                        if int(nB)==0: continue;
                        if nodeInfoCPT[0][j].mForward.has_key(self.sArgSubject):
                            nTransFwd *= nodeInfoCPT[0][j].mForward[self.sArgSubject];
                            pastAct = nodeInfoCPT[0][j];
#                             print iT, pastAct.name, actM.rootAct.name, bin(i)[2:].zfill(nLinks);
                        else:
                            nTransFwd *= nodeInfoCPT[0][j].mForward[nodeInfoCPT[0][j].aSubjects[0]];
                    nTransFwd *= nTransP;
#                     print "*", iT, nTransFwd, pastAct.name, actM.rootAct.name;
                    if nTransFwd>=nMax_TransFwd: #and pastAct!=None:
                        nMax_TransFwd = nTransFwd;
#                         if pastAct==None:
#                             print pastAct, actM.rootAct.name;
#                         else:
#                             print pastAct.name, actM.rootAct.name;
                        maxPastAct = pastAct;
            aMax_Forward.append(nObsProb * nMax_TransFwd);
            aMaxPastAct.append(maxPastAct);
        #Store Forward & Max_PastAct of each State
        aMax_Forward = self.normalize(aMax_Forward);
        for i,actM in enumerate(self.aStates[iT]): #Store these Forward Probs & sMaxPast_NodeId into their Nodes
#             print "*Node:", actM.rootAct.name;
#             if aMaxPastAct[i]!=None:
#                 print "- MaxPastAct:", aMaxPastAct[i].name;
#             else:
#                 print "- MaxPastAct:", aMaxPastAct[i]
# #             print "- OldMaxForward:", actM.rootAct.mForward[self.sArgSubject];
            actM.rootAct.mForward[self.sArgSubject] = aMax_Forward[i];
            actM.rootAct.mMaxPast_NodeId[self.sArgSubject] = aMaxPastAct[i];
#             print "- MaxForward, in "+str(iT)+":", aMax_Forward[i];
            
    def normalize(self, aMax_Forward):
        nZ = sum(aMax_Forward); #Normalizer
        for i in range(len(aMax_Forward)):
            aMax_Forward[i] = aMax_Forward[i]/nZ;
        return aMax_Forward;
            
    def checkBestPast(self, i, nodeInfoCPT, pastAct, sActor, nMax_TransFwd, maxPastAct):        
        iParent = (len(nodeInfoCPT[0])-1)-i; #Reverse index to get correct parent Prob from CPT
        iTrans = 2**iParent;
        nTransP = nodeInfoCPT[1][iTrans];
#         nTransFwd = math.log(nTransP) + pastAct.nForward;
        
        nTransFwd = nTransP * pastAct.mForward[sActor];
        if nTransFwd>=nMax_TransFwd:
            nMax_TransFwd = nTransFwd;
            maxPastAct = pastAct;
        return [nMax_TransFwd, maxPastAct]
            
    def getBestPath(self):
        self.iMultArgs = -1;
        self.mMultArgs = dict();
        self.aBestPath = [];
        nMaxTime = len(self.aTimes)-1;
        nMaxFwd = float("-inf");
        for actM in self.aStates[nMaxTime]:
            if actM.rootAct.mForward[self.sArgSubject]>=nMaxFwd:
                nMaxFwd = actM.rootAct.mForward[self.sArgSubject];
                maxAct = actM.rootAct;
        nodeAct = maxAct;
        if self.mBestPath.has_key(nodeAct.aTimes[-1]):
            nodeAct = self.mBestPath[nodeAct.aTimes[-1]];
            self.aBestPath.append(nodeAct);
        else:
            self.aBestPath.append(nodeAct);
        while nodeAct.mMaxPast_NodeId.has_key(self.sArgSubject) and nodeAct.mMaxPast_NodeId[self.sArgSubject]!=None:
            sTime = nodeAct.mMaxPast_NodeId[self.sArgSubject].aTimes[-1];
            if self.mBestPath.has_key(sTime):
                best_Node = self.mBestPath[sTime];
                self.aBestPath.append(best_Node);
                if not(best_Node.mMaxPast_NodeId.has_key(self.sArgSubject)) or best_Node.mMaxPast_NodeId[self.sArgSubject]==None:
                    break;
                nodeAct = best_Node; #best_Node.mMaxPast_NodeId[self.sArgSubject];
            else:
                self.aBestPath.append(nodeAct.mMaxPast_NodeId[self.sArgSubject]);
                nodeAct = nodeAct.mMaxPast_NodeId[self.sArgSubject];
        self.aBestPath.reverse();
        #Find all activities with Multiple Subjects
        for i in range(len(self.aBestPath)):
            if self.getNumArgs(self.aBestPath[i])>1:
                self.mMultArgs[self.aTimes[i]] = self.aBestPath[i]; #Store All MultArg Activities by Time
                if self.iMultArgs<0:
                    self.iMultArgs = i #Store in iMultArgs only last activity
                    #self.iMultArgs = (len(self.aBestPath)-1)-i; #Store in iMultArgs only last activity
    
    def getNumArgs(self, actNode):
        aArgs = [];
        for sArg in actNode.aSubjects: #For each Arg (in this Act)
            if not(sArg in aArgs) and sArg!="*":
                aArgs.append(sArg);
        return len(aArgs);
        
    def getBestPath_Viterbi(self):
        bestPath = self.aBestPath;
        if len(bestPath)>len(self.aTimes):
            bestPath = bestPath[1:];
        aPath = [];
        for act in bestPath:
            aPath.append(act.name);
        return aPath;
    
    def getBestPath_MAP(self, aMAP, graph):
        aPath = [];
        for iT in range(len(self.aTimes)):
            for actM in self.aStates[iT]:
                if aMAP[graph.node2label[actM.rootAct]]==1:
                    aPath.append(actM.rootAct.name);
        return aPath;
    
    def getBestPath_VE(self, aNodes, aMAP_VE, graph):
        aPath = [];
        for iT in range(len(self.aTimes)):
            for actM in self.aStates[iT]:
                if not(actM.rootAct in aNodes): continue;
                iNode = aNodes.index(actM.rootAct);
                nVal = aMAP_VE[iNode];
                if nVal==1:
                    aPath.append(actM.rootAct.name);
        return aPath;        

    def getMaxProbSeq(self, iT, graph):
        print "\nTime:", self.aTimes[iT], "- HMM:", self.sArgSubject;
        aStates = [];
        #Clamp last biggest Post
        if len(self.aMaxSts)>0:
            for actM in self.aStates[iT-1]:
                graph.addobs(actM.rootAct, False);
            graph.addobs(self.aMaxSts[-1].rootAct, True);
        #Get & compute all activities Posts
        for actM in self.aStates[iT]: #Get Prob of each path PastSt->[State] Activity (of this Time Step)
            aStates.append(actM.rootAct);
            print "     - States:", actM.rootAct.name;
        aPosts = graph.postprob(aStates);
        print aPosts;
        #Find biggest Post
        nFinal_MaxProb = max(aPosts);
        iMaxP = aPosts.index(nFinal_MaxProb);
        maxAct = self.aStates[iT][iMaxP]; 
        #Store biggest Post
        if len(self.aProbSt)==0:
            self.aProbSt.append(nFinal_MaxProb);
        else:
            self.aProbSt.append(self.aProbSt[-1]*nFinal_MaxProb);
        print self.aProbSt; 
        self.aMaxSts.append(maxAct);
        #Unclamp last Posts
        if len(self.aMaxSts)>0:
            for actM in self.aStates[iT-1]:
                graph.removeObs(actM.rootAct);
    
    def getMaxProbSt(self, iT, graph):
        pass;
    
    def fillHmm(self, sTime, aActs_Time, graph):
        self.aTimes.append(sTime);
        self.getStates(sTime, aActs_Time);
    
    def updateHmm(self, sTime, aActs_Time, graph):
        self.aTimes.append(sTime);
        self.getStates(sTime, aActs_Time);
        self.getViterbi_ByLog(len(self.aTimes)-1, graph);
#         self.getMaxProbSeq(len(self.aTimes)-1, graph);
#         self.getProbSt(len(self.aTimes)-1, graph);
#         self.getMaxProbSt(len(self.aTimes)-1, graph);
        